﻿//
// *************************************************************************************
// WP-Framework
//
// Developed by 
// Lukas Kretschmar (lkretschmar.dev@bluewin.ch)
//
// Switzerland, Copyright (c) 2012 lkretschmar
// This content is released under the MIT License (http://opensource.org/licenses/MIT).
//   
// *************************************************************************************
//
//
			

using System;
using System.Linq;
using KretschIT.WP_Fx.EasyMoq.Calls.Actions;
using KretschIT.WP_Fx.EasyMoq.Calls.Functions;
using System.Linq.Expressions;
using KretschIT.WP_Fx.EasyMoq.Exceptions;
using KretschIT.WP_Fx.EasyMoq.Calls;
using KretschIT.WP_Fx.Core.Extensions;
using KretschIT.WP_Fx.Core.Validation;
using Expression = System.Linq.Expressions.Expression;
using KretschIT.WP_Fx.EasyMoq.I18n;
using KretschIT.WP_Fx.EasyMoq.Adapters;
using System.Reflection;
using KretschIT.WP_Fx.Core.Converters;

namespace KretschIT.WP_Fx.EasyMoq
{
    internal class Mock<T> : IMock<T>, IHandler<T> where T : class
    {
        private readonly ICallHandler<ActionMock<T>, IActionSetupItem<T>> actions = new CallHandler<ActionMock<T>, IActionSetupItem<T>>((m, a) => new ActionSetupItem<T>(m, a));
        private readonly ICallHandler<FunctionMock<T>, IFunctionSetupItem<T>> functions = new CallHandler<FunctionMock<T>, IFunctionSetupItem<T>>((m, f) => new FunctionSetupItem<T>(m, f));
        private readonly IPropertyHandler<T> properties;

        private BaseMockFactory factory;

        #region Ctor

        internal Mock(BaseMockFactory factory)
        {
            It.IsNotNull(() => factory);

            this.factory = factory;
            properties = new PropertyHandler<T>(this.Object);
        }

        #endregion

        #region Setup

        public IAction<T> Setup(Expression<Action<T>> call)
        {
            this.IsNotNull(call);

            return this.actions.Setup(call);
        }        

        public IFunction<T, TResult> Setup<TResult>(Expression<Func<T, TResult>> call)
        {
            this.IsNotNull(call);
            this.IfIs<MemberExpression>(call, m => new SetupException(m), () => Messages.Info_SetupProperty);

            var item = this.functions.Setup(call);
            return new FunctionMockAdapter<T, TResult>(item);
        }
   
        public IPropertyGetter<T, TResult> SetupGet<TResult>(Expression<Func<T, TResult>> property)
        {
            this.IsNotNull(property);
            this.IfIs<MethodCallExpression>(property, m => new SetupException(m), () => Messages.Info_SetupMethod);

            var item = this.properties.Setup(property.To<MemberExpression>());
            return new PropertyMockAdapter<T, TResult>(item);
        }

        public IPropertySetter<T> SetupSet<TResult>(Expression<Func<T, TResult>> property)
        {
            this.IsNotNull(property);
            this.IfIs<MethodCallExpression>(property, m => new SetupException(m), () => Messages.Info_SetupMethod);

            var item = this.properties.Setup(property.To<MemberExpression>());
            return (IPropertySetter<T>)item;
        }

        #endregion

        #region Raises

        public void Raises(Action<IEventsOf<T>> eventCall)
        {
            eventCall((IEventsOf<T>)this.Object);
        }

        #endregion

        #region Call

        public void Call(Expression<Action<T>> call)
        {
            var items = this.actions.Call(call);           
            foreach (var item in items)
            {
                item.Execute(this.Object);
            }
        }

        public TResult Call<TResult>(Expression<Func<T, TResult>> call)
        {
            var items = this.functions.Call(call);
            TResult result = default(TResult);
            foreach(var item in items)
            {
                var value = item.Execute(this.Object);
                if (!(value is EmptyValue))
                {
                    result = Try.Cast(value, v => (TResult)v);
                }
            }
            return result;
        }

        public TResult Get<TResult>(Expression<Func<T, TResult>> property)
        {
            var value = this.properties.GetValue(property.To<MemberExpression>());
            return Try.Cast(value, v => (TResult)v);
        }

        public void Set<TResult>(Expression<Func<T, TResult>> property, TResult value)
        {
            this.properties.SetValue(property.To<MemberExpression>(), value);
        }

        public void Set<TResult>(Func<T, PropertyInfo> property, TResult value)
        {
            this.properties.SetValue(property(this.Object), value);
        }

        #endregion

        #region Verify

        public void Verify(Expression<Action<T>> call, ICondition condition = null)
        {
            It.IsNotNull(() => call);

            var calls = this.actions.Calls(call);
            this.Verify(condition, calls);
        }

        public void Verify<TResult>(Expression<Func<T, TResult>> call, ICondition condition = null)
        {
            this.IsNotNull(call);
            this.IfIs<MemberExpression>(call);

            var calls = this.functions.Calls(call);
            this.Verify(condition, calls);
        }
        
        public void VerifyGet<TResult>(Expression<Func<T, TResult>> property, ICondition condition = null)
        {
            this.VerifyProperty((h, m) => h.GetterCalls(m), property, condition); 
        }

        public void VerifySet<TResult>(Expression<Func<T, TResult>> property, ICondition condition = null)
        {
            this.VerifyProperty((h, m) => h.SetterCalls(m), property, condition); 
        }
        
        private void VerifyProperty<TResult>(Func<IPropertyHandler<T>, MemberExpression, int> getCallsOf, Expression<Func<T, TResult>> property, ICondition condition = null)
        {
            this.IsNotNull(property);
            this.IfIs<MethodCallExpression>(property);

            var calls = getCallsOf(this.properties, property.To<MemberExpression>());
            this.Verify(condition, calls);
        }

        public void VerifySetter(Func<T, PropertyInfo> property, ICondition condition = null)
        {
            this.IsNotNull(property);
            this.VerifyProperty((h, m) => h.SetterCalls(m), property(this.Object), condition);
        }

        private void VerifyProperty(Func<IPropertyHandler<T>, PropertyInfo, int> getCallsOf, PropertyInfo property, ICondition condition = null)
        {
            this.IsNotNull(property);
            
            var calls = getCallsOf(this.properties, property);
            this.Verify(condition, calls);
        }
 
        private void Verify(ICondition condition, int calls)
        {
            condition = this.GetCondition(condition);
            if (!condition.CanBeVerifiedWith(calls))
            {
                throw new VerificationException(condition.GetFailureMessage(calls));
            }
        }
        
        private ICondition GetCondition(ICondition condition)
        {
            if (condition == null)
            {
                condition = this.DefaultVerificationCondition;
            }
            return condition;
        }
        
        public void SetDefaultVerificationCondition(ICondition condition)
        {
            this.DefaultVerificationCondition = condition;
        } 

        #region DefaultVerificationCondition

        private ICondition defaultVerificationCondition;
        
        private ICondition DefaultVerificationCondition
        {           
            get
            {
                if (defaultVerificationCondition != null)
                {
                    return this.defaultVerificationCondition;
                }
                return Condition.AtLeastOnce();
            }
            set
            {
                this.defaultVerificationCondition = value;
            }
        }

        #endregion

        #endregion

        #region Validation

        private void IsNotNull(LambdaExpression lambda)
        {
            if (lambda == null)
            {
                throw new ArgumentNullException("call");
            }
        }

        private void IsNotNull(Func<T, PropertyInfo> property)
        {
            if (property == null)
            {
                throw new ArgumentNullException("property");
            }
        }

        private void IsNotNull(PropertyInfo property)
        {
            if (property == null)
            {
                throw new ArgumentNullException("property");
            }
        }

        private void IfIs<TExpression>(LambdaExpression lambda, Func<string, Exception> createException = null, Func<string> info = null) where TExpression : Expression
        {
            if (lambda.Body is TExpression)
            {
                var message = GetExpressionMissmatchMessage<MemberExpression>(lambda, info);
                if (createException == null)
                {
                    throw new ArgumentException(message);
                }
                throw createException(message);
            }
        }

        private string GetExpressionMissmatchMessage<TExpected>(LambdaExpression call, Func<string> info) where TExpected : Expression
        {
            var message = string.Format(Messages.Exception_ExpressionMissmatchFormat, call.Body.GetType().FullName, typeof(TExpected).FullName);
            if (info != null)
            {
                message += info();
            }
            return message;
        }

        #endregion

        #region Object

        private T instance;

        public T Object
        {
            get 
            {
                if (this.instance == null)
                {
                    this.instance = factory.Resolve<T>(this);
                }
                return this.instance;
            }
        }

        #endregion
    }
}
